//
// Copyright (c) 2009 All Right Reserved
//
// vl
//
// 2009-01-01
// Contains ...
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using LargoCommon.Abstract;
namespace LargoCommon.Music
{
/// Harmonic system.
/// Harmonic system is subclass of binary GSystem. It is defined by its Order, that represents number
/// of tones in one octave. In addition to that, array of symbols (c,c#,d,..)
/// and array of (formal) intervals (0,1,2,..) are created for given Order.
[Serializable]
[XmlRoot]
public sealed class HarmonicSystem : GeneralSystem {
#region Fields
///
/// Naming base 24.
///
public const byte NamingBase24 = 24;
///
/// Naming base 48.
///
public const byte NamingBase48 = 48;
/// Maximal Continuity Quantum (3).
public const float C1 = 6.0f;
/// Continuity Quantum (5).
public const float C2 = 3.0f;
/// Continuity Quantum (7).
public const float C3 = 1.0f;
/// Maximal Impulse Quantum (halftone).
public const float I1 = 12.0f;
/// Consonance quotient.
public const float ConsonanceRatio = 4.0f;
/// Constant determining size of interval store. This optimization is switched off now.
public const int MaximumLengthOfStoredRealInterval = 0;
/// Used Systems.
private static readonly Dictionary UsedSystems = new Dictionary();
/// Consonance quotient.
private readonly Dictionary intervalRatios;
/// Musical sharp symbols.
private readonly string[] sharpSymbols;
/// Musical sharp symbols.
private readonly string[] flatSymbols;
/// List of intervals.
private Collection pitches;
/// List of intervals.
private Collection intervals;
//// Shortcuts of harmonic structures.
//// private Dictionary harStructShortcuts;
//// Shortcuts of harmonic structures.
//// private Dictionary harStructShortcutsMoot;
#endregion
#region Constructors
/// Initializes a new instance of the HarmonicSystem class. Serializable.
public HarmonicSystem() {
}
/// Initializes a new instance of the HarmonicSystem class.
/// Order of system.
public HarmonicSystem(byte order)
: base(2, order) {
if (order == 0) {
throw new ArgumentException("Order of system must not be 0.");
}
this.intervalRatios = new Dictionary();
this.sharpSymbols = this.MakeSymbolArray(true);
this.flatSymbols = this.MakeSymbolArray(false);
this.StringOfSharpSymbols = SymbolsToString(this.sharpSymbols);
this.StringOfFlatSymbols = SymbolsToString(this.flatSymbols);
this.MakePitches();
this.MakeIntervals();
}
/// Initializes a new instance of the HarmonicSystem class.
/// Degree of system.
/// Order of system.
public HarmonicSystem(byte degree, byte order)
: base(degree, order) {
}
#endregion
#region Properties
/// Gets list of pitches.
/// Property description.
[XmlIgnore]
public Collection Pitches {
get {
Contract.Ensures(Contract.Result>() != null);
if (this.pitches == null) {
throw new InvalidOperationException("List of pitches is null.");
}
return this.pitches;
}
}
/// Gets list of intervals.
/// Property description.
[XmlIgnore]
public Collection Intervals {
get {
Contract.Ensures(Contract.Result>() != null);
if (this.intervals == null) {
throw new InvalidOperationException("List of intervals is null.");
}
return this.intervals;
}
}
/// Gets or sets string of musical symbols.
/// Property description.
[XmlIgnore]
public string StringOfSharpSymbols { get; set; }
/// Gets or sets string of musical symbols.
/// Property description.
[XmlIgnore]
public string StringOfFlatSymbols { get; set; }
///
/// Gets the chromatic modality.
///
/// Property description.
public HarmonicModality ChromaticModality {
get {
//// long chromatic = musicalBar.FileModel.MusicalBlock.HarmonicSystem.LongSize() - 1; // (long)(Math.Pow(2, this.Order) - 1);
var order = this.Order;
var chromaticCode = "0"; //// new byte[order + 2]; // chromaticCode[0] = 0; chromaticCode[1] = (byte)'#';
for (var i = 0; i < order; i++) {
chromaticCode += ",1";
}
var modality = HarmonicModality.GetNewHarmonicModality(this, chromaticCode.ToString(CultureInfo.CurrentCulture));
return modality;
}
}
#endregion
#region Static methods
///
/// Gets Harmonic System.
///
/// System order.
/// Returns value.
public static HarmonicSystem GetHarmonicSystem(byte order) {
Contract.Ensures(Contract.Result() != null);
var hm = UsedSystems.ContainsKey(order) ? UsedSystems[order] : null;
if (hm != null) {
return hm;
}
hm = new HarmonicSystem(order);
UsedSystems[order] = hm;
return hm;
}
///
/// Formal measure of dissonance, higher for consonant structures.
///
/// Harmonic Continuity.
/// Harmonic Impulse.
/// Returns value.
public static float Consonance(float continuity, float impulse) {
// float formalConsonance = aFContinuity-HarmonicSystem.ciSonanceRatio*aFImpulse;
// float num = formalConsonance+HarmonicSystem.ciSonanceRatio*HarmonicSystem.i1;
// float denominator = aFContinuity+HarmonicSystem.ciSonanceRatio*HarmonicSystem.i1;
// formalConsonance = num/denominator*100f; // normalized to 0..100
var formalConsonance = (Math.Abs(continuity) + (ConsonanceRatio * (100 - impulse))) / (ConsonanceRatio + 1);
return formalConsonance;
}
#endregion
#region Public methods
/// Returns ratio of the interval.
/// Real system length.
/// Returns value.
public float RatioForInterval(int sysLength) {
float ratio;
if (sysLength == 0) {
return 1.0f;
}
if (sysLength == this.Order) {
return 2.0f;
}
if (sysLength == -this.Order) {
return DefaultValue.HalfUnit;
}
if (this.intervalRatios.ContainsKey(sysLength)) {
ratio = this.intervalRatios[sysLength];
}
else {
ratio = (float)Math.Pow(2.0, ((float)sysLength) / this.Order);
this.intervalRatios[sysLength] = ratio;
}
return ratio;
}
///
/// Gets musical pitch.
///
/// System altitude.
/// Returns value.
public MusicalPitch GetPitch(int systemAltitude) {
if (systemAltitude < 0) {
return null;
}
return systemAltitude < this.Pitches.Count ? this.Pitches[systemAltitude] : null;
}
#endregion
#region MIDI support
/// Returns interval Size in halftones, because of MIDI.
/// Real system length.
/// Returns value.
public float HalftonesForInterval(int sysLength) {
if (this.Order == DefaultValue.HarmonicOrder) {
return sysLength;
}
var ratio = this.RatioForInterval(sysLength);
var r1 = (float)Math.Pow(2.0, 1.0 / DefaultValue.HarmonicOrder);
var lgr1 = (float)Math.Log(r1);
var halftones = lgr1 >= DefaultValue.AfterZero ? (float)Math.Round(Math.Log(ratio) / lgr1, 4) : 0;
return halftones;
}
#endregion
#region Intervals
/// Returns tone index of the given interval.
/// Number of tone symbols.
/// Formal system length.
/// Returns value.
public short GuessToneIndex(byte baseNumber, byte formalLength) {
const float afterZero = 0.0001f;
Contract.Requires(baseNumber > 0);
var ratio = this.RatioForInterval(formalLength);
var discreteRatio = (float)Math.Pow(2.0, DefaultValue.HalfUnit / baseNumber);
var limit = discreteRatio + afterZero;
for (byte i = 0; i < baseNumber; i++) {
discreteRatio = (float)Math.Pow(2.0, ((float)i) / baseNumber);
if (MathSupport.EqualNumbersRational(ratio, discreteRatio, limit)) {
return i;
}
}
return -1;
}
/// Returns name for the given interval.
/// Formal system length.
/// Returns value.
public string GuessNameForInterval(byte formalLength) {
string[] name24 = {
"unison",
"dim.second", "min.second", "mid.second", "maj.second", "aug.second",
"min.third", "mid.third", "maj.third",
"dim.fourth", "fourth", "aug.fourth",
"triton",
"dim.fifth", "fifth", "aug.fifth",
"min.sixth", "mid.sixth", "maj.sixth",
"dim.seventh", "min.seventh", "mid.seventh", "maj.seventh", "aug.seventh" };
var toneIndex = this.GuessToneIndex(NamingBase24, formalLength);
var value = toneIndex >= 0 && toneIndex < name24.Length ? name24[toneIndex] : "?" + formalLength.ToString(CultureInfo.CurrentCulture.NumberFormat);
return value;
}
#endregion
#region Substructures
///
/// Modality Classes.
///
/// General Qualifier.
/// Upper limit.
/// Returns value.
public Collection ModalityClasses(GeneralQualifier genQualifier, int limit) {
var hv = StructuralVarietyFactory.NewHarmonicModalityVariety(
StructuralVarietyType.BinaryClasses,
this,
genQualifier,
limit);
return new Collection(hv.StructList);
}
///
/// Modality Classes.
///
/// Returns value.
public Collection ModalityClasses() {
var hv = StructuralVarietyFactory.NewHarmonicModalityVariety(
StructuralVarietyType.BinaryClasses,
this,
null,
10000);
return new Collection(hv.StructList);
}
///
/// Modality Instances.
///
/// General Qualifier.
/// Upper limit.
/// Returns value.
public Collection ModalityInstances(GeneralQualifier genQualifier, int limit) {
var hv = StructuralVarietyFactory.NewHarmonicModalityVariety(
StructuralVarietyType.Instances,
this,
genQualifier,
limit);
return hv.StructList;
}
///
/// Modality Instances.
///
/// Returns value.
public Collection ModalityInstances() {
var hv = StructuralVarietyFactory.NewHarmonicModalityVariety(
StructuralVarietyType.Instances,
this,
null,
10000);
return hv.StructList;
}
///
/// Struct Classes.
///
/// General Qualifier.
/// Upper limit.
/// Returns value.
public Collection StructClasses(GeneralQualifier genQualifier, int limit) {
var hv = StructuralVarietyFactory.NewHarmonicStructuralVariety(
StructuralVarietyType.BinaryClasses,
this,
genQualifier,
limit);
return hv.StructList;
}
///
/// Struct Classes.
///
/// Returns value.
public Collection StructClasses() {
var hv = StructuralVarietyFactory.NewHarmonicStructuralVariety(
StructuralVarietyType.BinaryClasses,
this,
null,
10000);
return hv.StructList;
}
///
/// Struct Instances.
///
/// General Qualifier.
/// Upper limit.
/// Returns value.
public Collection StructInstances(GeneralQualifier genQualifier, int limit) {
var hv = StructuralVarietyFactory.NewHarmonicStructuralVariety(
StructuralVarietyType.Instances,
this,
genQualifier,
limit);
return hv.StructList;
}
///
/// Struct Instances.
///
/// Returns value.
public Collection StructInstances() {
var hv = StructuralVarietyFactory.NewHarmonicStructuralVariety(
StructuralVarietyType.Instances,
this,
null,
10000);
return hv.StructList;
}
#endregion
#region String representation
///
/// Returns symbols for given element.
///
/// Xml Element.
/// If set to true [sharp].
///
/// Returns value.
///
public string Symbol(short element, bool sharp) {
var symbols = sharp ? this.sharpSymbols : this.flatSymbols;
if (symbols != null && element >= 0 && element < symbols.Length) {
return symbols[element];
}
var formalElement = element % this.Order;
if (formalElement < 0) {
formalElement += this.Order;
}
var s = string.Empty;
if (symbols != null && (formalElement >= 0 && formalElement < symbols.Length)) {
return symbols[formalElement];
}
s = element < 0 ? s.ToUpper(CultureInfo.CurrentCulture) : s + "'";
return s;
}
///
/// Determines whether the specified element is enharmonic.
///
/// The element.
///
/// True if the specified element is enharmonic; otherwise, false.
///
public bool IsEnharmonic(short element) {
var s = this.sharpSymbols[element];
return s?.Trim().Length > 1;
}
///
/// Returns tone symbol for the given element.
///
/// Requested element.
/// If set to true [sharp].
///
/// Returns value.
///
public string GuessSymbolForElement(byte element, bool sharp) {
var value = "?";
const char b = 'b'; //// (char)0x0185;
if (this.Order <= NamingBase24) {
string[] sym24Sharp = { "c", "c+", "c#", "cx", "d", "d+", "d#", "dx", "e", "e+", "f", "f+",
"f#", "fx", "g", "g+", "g#", "gx", "a", "a+", "a#", "ax", "h", "h+" };
string[] sym24Flat = { "c", "c+", "d" + b, "d-", "d", "d+", "e" + b, "e-", "e", "e+", "f", "f+",
"g" + b, "g-", "g", "g+", "a" + b, "a-", "a", "a+", "b", "h-", "h", "h+" };
var toneIndex = this.GuessToneIndex(NamingBase24, element);
if (toneIndex >= 0 && toneIndex < NamingBase24) {
value = sharp ? sym24Sharp[toneIndex] : sym24Flat[toneIndex];
}
}
else {
string[] sym48Sharp = { "c", "c'", "c+", "c+'", "c#", "c#'", "cx", "cx'", "d", "d'", "d+", "d+'",
"d#", "d#'", "dx", "dx'", "e", "e'", "e+", "e+'", "f", "f'", "f+", "f+'",
"f#", "f#'", "fx", "fx'", "g", "g'", "g+", "g+'", "g#", "g#'", "gx", "gx'",
"a", "a'", "a+", "a+'", "a#", "a#'", "ax", "ax'", "h", "h'", "h+", "h+'" };
var toneIndex = this.GuessToneIndex(NamingBase48, element);
if (toneIndex >= 0 && toneIndex < NamingBase48) {
value = sym48Sharp[toneIndex];
}
}
return value;
}
/// String representation of the object.
/// Returns value.
public override string ToString() {
var s = new StringBuilder();
s.Append("Harmonic system\r\n");
s.Append(base.ToString() + Environment.NewLine);
foreach (var hi in this.Intervals.Where(hi => hi != null)) {
s.Append(hi + Environment.NewLine);
}
return s.ToString();
}
#endregion
#region Private static methods
///
/// Symbols the array to string.
///
/// The symbols.
/// Returns value.
private static string SymbolsToString(string[] symbols) {
var str = new StringBuilder();
Array.ForEach(
symbols,
s => {
str.Append(s);
str.Append(",");
});
return str.ToString();
}
#endregion
#region Private methods
/// Makes array of pitch objects.
private void MakePitches() {
this.pitches = new Collection();
for (byte i = 0; i < 128; i++) {
var mp = new MusicalPitch(this, i);
this.Pitches.Add(mp);
}
}
/// Makes array of interval objects.
private void MakeIntervals() {
this.intervals = new Collection();
for (byte i = 0; i < this.Order; i++) {
var hI = new HarmonicInterval(this, i, 1.0f);
this.Intervals.Add(hI);
}
}
///
/// Makes array of symbols used in this GSystem.
///
/// If set to true [sharp].
/// Returns value.
private string[] MakeSymbolArray(bool sharp) {
var str = new StringBuilder();
var symbols = new string[this.Order];
for (byte i = 0; i < this.Order; i++) {
var s = this.GuessSymbolForElement(i, sharp);
str.Append(s);
if (i < this.Order - 1) {
str.Append(",");
}
if (i < symbols.Length) {
symbols[i] = s;
}
}
return symbols;
}
#endregion
}
}